package edu.amm.fanal.api;

import edu.amm.api.Bounds;

/**
 * Преобразование границ задачи.
 * 
 * @author Иванов Илья
 * @since 2013-05-11
 *
 */
public class BoundsTransformation extends Transformation {
	
	private double tau1;
	private double tau2;
	
	private Function forwardTransformation;
	private Function backwardTransformation;
	
	/**
	 * @param sourceProblem Исходная задача.
	 * @param newBounds Новые границы задачи.
	 */
	public BoundsTransformation(LinearProblem sourceProblem, Bounds newBounds) {
		super(sourceProblem);
		if (newBounds == null)
			throw new RuntimeException("Не заданы новые границы задачи");
		
		tau1 = newBounds.getLeftBound();
		tau2 = newBounds.getRightBound();
	}
	
	protected LinearProblem transform() {
		final double t1 = sourceProblem.getLeftBound();
		final double t2 = sourceProblem.getRightBound();
		final double coef = (t2 - t1) / (tau2 - tau1);
		
		forwardTransformation = new Function(BoundaryProblem.SOLUTION_DIM) {
			
			private Function derivative = Function.createIdentical(1, coef);
			
			protected double getValue(double... x) {
				return coef * (x[0] - tau1) + t1;
			}
			
			protected Function getDerivative(int variableNumber) {
				return derivative;
			}
		};
		
		backwardTransformation = new Function(BoundaryProblem.SOLUTION_DIM) {
			
			private double reciprocal = 1 / coef;
			private Function derivative = Function.createIdentical(1, reciprocal);
			
			protected double getValue(double... x) {
				return reciprocal * (x[0] - t1) + tau1;
			}
			
			protected Function getDerivative(int variableNumber) {
				return derivative;
			}
		};
		
		final Function p = sourceProblem.getP();
		final Function q = sourceProblem.getQ();
		final Function f = sourceProblem.getF();
		
		Function u = new Function(BoundaryProblem.SOLUTION_DIM) {
			protected double getValue(double... x) {
				return coef * p.evaluate(forwardTransformation.evaluate(x));
			}
		};
		
		final double square = coef * coef;
		
		Function v = new Function(BoundaryProblem.SOLUTION_DIM) {	
			protected double getValue(double... x) {
				return square * q.evaluate(forwardTransformation.evaluate(x));
			}
		};
		Function w = new Function(BoundaryProblem.SOLUTION_DIM) {
			protected double getValue(double... x) {
				return square * f.evaluate(forwardTransformation.evaluate(x));
			}
		};
		
		LinearProblem transformed = new LinearProblem(tau1, sourceProblem.getLeftValue(), tau2, sourceProblem.getRightValue());
		transformed.setP(u);
		transformed.setQ(v);
		transformed.setF(w);
		
		return transformed;
	}
	
	protected Function restore(final Function solution) {
		return new Function(BoundaryProblem.SOLUTION_DIM) {
			
			private Function dzdtau = solution.differentiate(0);
			private Function dtaudt = backwardTransformation.differentiate(0);
			
			private Function derivative = new Function(getDim()) {
				
				protected double getValue(double... x) {
					return dzdtau.evaluate(x) * dtaudt.evaluate(x);
				}
			};
			
			protected double getValue(double... x) {
				return solution.evaluate(backwardTransformation.evaluate(x));
			}
			
			protected Function getDerivative(int variableNumber) {
				return derivative;
			}
		};
	}
}